#pragma once

#include <boost/unordered_set.hpp>
#include <logger/stream.hpp>
#include <engine.core/System.hpp>
#include <engine.script/System.hpp>

#include "exports.hpp"
#include "ComponentSystem.hpp"
#include "Object.hpp"
#include "CameraComponent.hpp"




namespace Engine
{
	namespace Entity
	{
		class ENGINE_ENTITY_API System
			: public Engine::System
		{
			friend Engine::Entity::Object;

		protected:

			System(const EngineInfo& info,
				Video::Device::ptr videoDevice,
				Input::Device::ptr inputDevice,
				Script::System::ptr scriptSystem);

		public:

			SHARED(System);
			MAKE_SHARED4(System, EngineInfo, Video::Device::ptr, Input::Device::ptr, Script::System::ptr);
			virtual ~System();

			/**
			 * @returns the name of this system
			 */
			QString name() const;




			/**
			 * Creates a new entity, stores and returns it.
			 */
			Object* createEntity();

			/**
			 * Detaches all components from the given entity and calls release on the entity.
			 * If the entity still has some references to it, the actual object will not be destroyed.
			 */
			void destroyEntity(Object* entity);

			/**
			 * @returns the camera component
			 */
			CameraComponent* cameraComponent() const;



			/**
			 * Updates all components.
			 * Updates are batched by component id, therefore all components of the same id are updated
			 * first.
			 */
			void update();



			/**
			 * @returns a pointer to the script system
			 */
			Script::System::ptr scriptSystem() const;



			/**
			 * Registers the script system type with angelscript.
			 */
			static void registerType(Script::System::ptr engine);

		protected:

			bool attach(Object* entity, Component::ptr component);
			bool detach(Object* entity, Component::ptr component);
			void detach_all(Object* entity);

			bool attachUserComponent(Object* entity, Component::ptr component, unsigned int id);
			bool detachUserComponent(Object* entity, Component::ptr component, unsigned int id);

			/**
			 * Detaches all components from the given entity.
			 * This function is NOT part of the script interface.
			 * This function MAY NOT be called from any function other than
			 * the destructor of Entity::Object.
			 */
			void destroyed(Object* entity);

			/**
			 * @returns the component system for the given component id
			 */
			ComponentSystem& componentSystem(unsigned int id);

			void dummy()
			{}

		private:

			typedef boost::unordered_set<Object::ptr>                          Entities;
			typedef boost::unordered_map<unsigned int, ComponentSystem::ptr>   ComponentSystems;

			logger::stream::ptr   m_logger;

			Video::Device::ptr    m_videoDevice;
			Input::Device::ptr    m_inputDevice;
			Script::System::ptr   m_scriptSystem;

			Entities              m_entities;

			// Predefined systems
			ComponentSystem        m_physicsSystem;
			ComponentSystem        m_positionSystem;
			ComponentSystem        m_cameraSystem;

			ComponentSystems       m_userSystems;

			// Predefined components
			CameraComponent::ptr   m_cameraComponent;
		};
		///////////////////////////////////////////////////////////////////////////////////////////
	}
	///////////////////////////////////////////////////////////////////////////////////////////////
}
///////////////////////////////////////////////////////////////////////////////////////////////////
